using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Drawing;

namespace LabyrinthProject
{
    public class LabyrinthGenerator
    {
        #region fields
        Random randomizer = new Random();
        const int TEXT_UNIT = 2;
        #endregion

        #region Methods
        public Cell[,] GenerateMaze( int width, int height )
        {
            Cell[,] maze = CreateMaze( width, height );
            InitMaze( maze, width, height );
            maze = DFSMaze(maze, width, height);
            MakeCenter(maze, width, height);

            return maze;
        }

        public Bitmap GenerateTexture( Cell[,] maze, int width, int height )
        {
            Bitmap texture = new Bitmap( width * TEXT_UNIT + 1, height * TEXT_UNIT + 1 );
            for ( int i = 0 ; i < width * TEXT_UNIT + 1 ; i++ )
            {
                for ( int j = 0 ; j < height * TEXT_UNIT + 1 ; j++ )
                    texture.SetPixel( i, j, Color.White );
            }
            for ( int i = 0 ; i < width ; i++ )
            {
                for ( int j = 0 ; j < height ; j++ )
                {
                    if ( maze[i, j].walls[0] )
                    {
                        for ( int k = i * TEXT_UNIT ; k < ( i + 1 ) * TEXT_UNIT + 1 ; k++ )
                        {
                            texture.SetPixel( k, j * TEXT_UNIT, Color.Black );
                        }
                    }
                    if ( maze[i, j].walls[1] )
                    {
                        for ( int k = i * TEXT_UNIT ; k < ( i + 1 ) * TEXT_UNIT + 1 ; k++ )
                        {
                            texture.SetPixel( k, ( j + 1 ) * TEXT_UNIT, Color.Black );
                        }
                    }
                    if ( maze[i, j].walls[2] )
                    {
                        for ( int k = j * TEXT_UNIT ; k < ( j + 1 ) * TEXT_UNIT + 1 ; k++ )
                        {
                            texture.SetPixel( i * TEXT_UNIT, k, Color.Black );
                        }
                    }
                    if ( maze[i, j].walls[3] )
                    {
                        for ( int k = j * TEXT_UNIT ; k < ( j + 1 ) * TEXT_UNIT + 1 ; k++ )
                        {
                            texture.SetPixel( ( i + 1 ) * TEXT_UNIT, k, Color.Black );
                        }
                    }
                    if ( maze[i, j].player1 )
                    {
                        texture.SetPixel( i * TEXT_UNIT + TEXT_UNIT / 2, j * TEXT_UNIT + TEXT_UNIT / 2, Color.Green );
                    }
                    if ( maze[i, j].player2 )
                    {
                        texture.SetPixel( i * TEXT_UNIT + TEXT_UNIT / 2, j * TEXT_UNIT + TEXT_UNIT / 2, Color.Red );
                    }
                    if ( maze[i, j].finish )
                    {
                        texture.SetPixel( i * TEXT_UNIT + TEXT_UNIT / 2, j * TEXT_UNIT + TEXT_UNIT / 2, Color.Yellow );
                    }
                }
            }

            texture.Save("d:\\tekstura.bmp");
            return texture;
        }

        private Cell[,] CreateMaze( int width, int height )
        {
            Cell[,] maze = new Cell[width, height];
            for ( int i = 0 ; i < width ; i++ )
            {
                for ( int j = 0 ; j < height ; j++ )
                {
                    Cell cell = new Cell();
                    maze[i, j] = cell;
                }
            }

            return maze;
        }

        private void InitMaze( Cell[,] maze, int width, int height )
        {
            InitBorders( maze, width, height );
            InitCellPos( maze, width, height );
            //InitCellRoles(maze, width, height);
        }

        private void InitBorders( Cell[,] maze, int width, int height )
        {
            // left upper corner
            maze[0, 0].border[0] = true;
            maze[0, 0].border[2] = true;
            // right upper corner
            maze[width - 1, 0].border[0] = true;
            maze[width - 1, 0].border[3] = true;
            // left lower corner
            maze[0, height - 1].border[1] = true;
            maze[0, height - 1].border[2] = true;
            // right lower corner
            maze[width - 1, height - 1].border[1] = true;
            maze[width - 1, height - 1].border[3] = true;

            // upper and lower borders
            for ( int i = 1 ; i < width - 1 ; i++ )
            {
                maze[i, 0].border[0] = true;
                maze[i, height - 1].border[1] = true;
            }

            // left and right borders
            for ( int i = 1 ; i < height - 1 ; i++ )
            {
                maze[0, i].border[2] = true;
                maze[width - 1, i].border[3] = true;
            }
        }
        private void InitCellPos( Cell[,] maze, int width, int height )
        {
            for ( int i = 0 ; i < width ; i++ )
            {
                for ( int j = 0 ; j < height ; j++ )
                {
                    maze[i, j].position[0] = i;
                    maze[i, j].position[1] = j;
                }
            }
        }
        private void InitCellRoles( Cell[,] maze, int width, int height )
        {
            maze[0, height / 2].player1 = true;
            maze[width - 1, height / 2].player2 = true;
            maze[width / 2, height / 2].finish = true;
        }

        private void MakeCenter( Cell[,] maze, int width, int height )
        {
            int startWidth = width / 2 - 1;
            int startHeight = height / 2 - 1;
            for (int i = startWidth; i < (startWidth + 2); i++)
            {
                for (int j = startHeight; j < (startHeight + 2); j++)
                {
                    if (i != startWidth + 1)
                    {
                        maze[i, j].walls[3] = false;
                        maze[i + 1, j].walls[2] = false;
                    }
                    if (j != startHeight + 1)
                    {
                        maze[i, j].walls[1] = false;
                        maze[i, j + 1].walls[0] = false;
                    }
                }
            }
        }

        private Cell[,] DFSMaze( Cell[,] maze, int width, int height )
        {
            int initX = randomizer.Next( 0, width );
            int initY = randomizer.Next( 0, height );

            Stack<Cell> CellStack = new Stack<Cell>();
            int TotalCells = width * height;
            int VisitedCells = 1;
            Cell currentCell = maze[initX, initY];

            int choice;
            List<Cell> neighbourList = new List<Cell>();

            while ( true )
            {
                neighbourList = FindNeighbours( currentCell, maze, height, width );
                if ( neighbourList.Count != 0 )
                {
                    choice = randomizer.Next( 0, neighbourList.Count );
                    KnockDownWall( currentCell, neighbourList[choice] );
                    CellStack.Push( currentCell );
                    currentCell = neighbourList[choice];
                    VisitedCells++;
                }
                else
                {
                    currentCell = CellStack.Pop();
                    if ( CellStack.Count == 0 )
                        break;
                }
            }

            return maze;
        }

        private List<Cell> FindNeighbours( Cell cell, Cell[,] maze, int width, int height )
        {
            List<Cell> listNeigh = new List<Cell>();
            int newX;
            int newY;
            //find up
            if ( cell.border[0] != true )
            {
                newX = cell.position[0];
                newY = cell.position[1] - 1;
                if ( CheckWallsUp( maze[newX, newY] ) )
                    listNeigh.Add( maze[newX, newY] );
            }
            //find down
            if ( cell.border[1] != true )
            {
                newX = cell.position[0];
                newY = cell.position[1] + 1;
                if ( CheckWallsUp( maze[newX, newY] ) )
                    listNeigh.Add( maze[newX, newY] );
            }
            //find left
            if ( cell.border[2] != true )
            {
                newX = cell.position[0] - 1;
                newY = cell.position[1];
                if ( CheckWallsUp( maze[newX, newY] ) )
                    listNeigh.Add( maze[newX, newY] );
            }
            //find right
            if ( cell.border[3] != true )
            {
                newX = cell.position[0] + 1;
                newY = cell.position[1];
                if ( CheckWallsUp( maze[newX, newY] ) )
                    listNeigh.Add( maze[newX, newY] );
            }

            return listNeigh;
        }
        private bool CheckWallsUp( Cell cell )
        {
            for ( int i = 0 ; i < 4 ; i++ )
            {
                if ( cell.walls[i] == false )
                    return false;
            }
            return true;
        }
        private void KnockDownWall( Cell cell_1, Cell cell_2 )
        {
            if ( cell_1.position[0] == cell_2.position[0] )
                if ( cell_1.position[1] < cell_2.position[1] )
                {
                    cell_1.walls[1] = false;
                    cell_2.walls[0] = false;
                }
                else
                {
                    cell_1.walls[0] = false;
                    cell_2.walls[1] = false;
                }
            else
                if ( cell_1.position[0] < cell_2.position[0] )
                {
                    cell_1.walls[3] = false;
                    cell_2.walls[2] = false;
                }
                else
                {
                    cell_1.walls[2] = false;
                    cell_2.walls[3] = false;
                }
        }
        #endregion
    }
}
